#include "texture.h"
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
extern "C"
{
#include <jpeglib.h>
#include <jerror.h>
#include "tga.h"
}
#include <png.h>
#include <cstring>

#include <iostream>
using namespace std;
/////////
//Image//
/////////
Image::Image() : sizeX(0), sizeY(0), data(NULL)
{
}

Image::~Image()
{
  if (data != NULL) {delete[] data;}
}

///////////////
//ImageLoader//
///////////////
int ImageLoader::loadBMP(const char *filename, Image *image) {
    FILE *file;
    unsigned long size;                 // size of the image in bytes.
    unsigned long i;                    // standard counter.
    unsigned short int planes;          // number of planes in image (must be 1) 
    unsigned short int bpp;             // number of bits per pixel (must be 24)
    unsigned char *tmp;                 // temporary color storage for bgr-rgba conversion.

    // make sure the file is there.
    if ((file = fopen(filename, "rb"))==NULL)
    {
	printf("File Not Found : %s\n",filename);
	return 0;
    }
    
    // seek through the bmp header, up to the width/height:
    fseek(file, 18, SEEK_CUR);

    // read the width
    if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
	printf("Error reading width from %s.\n", filename);
	return 0;
    }
    printf("Width of %s: %lu\n", filename, image->sizeX);
    
    // read the height 
    if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
	printf("Error reading height from %s.\n", filename);
	return 0;
    }
    printf("Height of %s: %lu\n", filename, image->sizeY);
    
    // calculate the size (assuming 24 bits or 3 bytes per pixel).
    size = image->sizeX * image->sizeY * 3;

    // read the planes
    if ((fread(&planes, 2, 1, file)) != 1) {
	printf("Error reading planes from %s.\n", filename);
	return 0;
    }
    if (planes != 1) {
	printf("Planes from %s is not 1: %u\n", filename, planes);
	return 0;
    }

    // read the bpp
    if ((i = fread(&bpp, 2, 1, file)) != 1) {
	printf("Error reading bpp from %s.\n", filename);
	return 0;
    }
    if (bpp != 24) {
	printf("Bpp from %s is not 24: %u\n", filename, bpp);
	return 0;
    }
	
    // seek past the rest of the bitmap header.
    fseek(file, 24, SEEK_CUR);

    // read the data. 
    image->data = new unsigned char[size];
    if (image->data == NULL) {
	printf("Error allocating memory for color-corrected image data");
	return 0;	
    }

    if ((i = fread(image->data, size, 1, file)) != 1) {
	printf("Error reading image data from %s.\n", filename);
	return 0;
    }
    
    //convert bgr-> rgba
    tmp = new unsigned char[image->sizeX * image->sizeY *4];
    memset(tmp,image->sizeX * image->sizeY *4,0xFF);
    for (i=0;i<size;i+=3) { // reverse all of the colors. (bgr -> rgb)
	/*temp = image->data[i];
	image->data[i] = image->data[i+2];
	image->data[i+2] = temp;*/
	*(tmp++) = image->data[i+2];//Red
	*(tmp++) = image->data[i+1];//Green
	*(tmp++) = image->data[i];//Blue
	*(tmp++) = 0xFF;//Alpha
    }
    //delete[] image->data;
    image->data = tmp;
    
    return 1;

}


void ImageLoader::loadJPEG(const char *fichier, Image*image)
{
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;
  FILE *file;	
  unsigned char *ligne;

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);
  if ((file=fopen(fichier,"rb"))==NULL)
    {
      fprintf(stderr,"Erreur : impossible d'ouvrir le fichier texture.jpg\n");
      exit(1);
    }
  jpeg_stdio_src(&cinfo, file);
  jpeg_read_header(&cinfo, TRUE);

  int size = cinfo.image_width * cinfo.image_height * 3;
  image->data = new unsigned char[size];
  image->sizeX = cinfo.image_width;
  image->sizeY = cinfo.image_height;
  if (cinfo.jpeg_color_space==JCS_GRAYSCALE) {
    fprintf(stdout,"Erreur : l'image doit etre de type RGB\n");
    exit(1);
  }

  jpeg_start_decompress(&cinfo);
  ligne=image->data;
  int taille_ligne = cinfo.image_width;
  while (cinfo.output_scanline<cinfo.output_height)
    {
      ligne=image->data+3*taille_ligne*cinfo.output_scanline;
      jpeg_read_scanlines(&cinfo,&ligne,1);
    }
  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  
  unsigned char *tmp = new unsigned char[image->sizeX * image->sizeY *4];
  memset(tmp,image->sizeX * image->sizeY *4,0xFF);
  for (int i=0,j=0;i<size;i+=3,j+=4) { // reverse all of the colors. (bgr -> rgb)
    tmp[j] = image->data[i];//Red
    tmp[j+1] = image->data[i+1];//Green
    tmp[j+2] = image->data[i+2];//Blue
    tmp[j+3] = 0xFF;//Alpha
    }
    //delete[] image->data;
    image->data = tmp;
}

void ImageLoader::loadPNG(const char *fichier, Image *img)
{
  png_byte magic[8];
  png_structp png_ptr;
  png_infop info_ptr;
  int bit_depth, color_type;
  FILE *fp = NULL;
  png_bytep *row_pointers = NULL;
  unsigned long i;

  /* open image file */
  fp = fopen (fichier, "rb");
  if (!fp)
  {
    fprintf (stderr, "error: couldn't open \"%s\"!\n", fichier);
    return ;
  }

  /* read magic number */
  fread (magic, 1, sizeof (magic), fp);
  /* check for valid magic number */
  if (!png_check_sig (magic, sizeof (magic)))
  {
     fprintf (stderr, "error: \"%s\" is not a valid PNG image!\n",fichier);
     fclose (fp);
     return ;
  }
  /* create a png read struct */
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!png_ptr)
  {
     fclose (fp);
     return ;
  }
  /* create a png info struct */
  info_ptr = png_create_info_struct (png_ptr);
  if (!info_ptr)
  {
     fclose (fp);
     png_destroy_read_struct (&png_ptr, NULL, NULL);
     return ;
  }
  /* initialize the setjmp for returning properly after a libpng
   * error occured */
  if (setjmp (png_jmpbuf (png_ptr)))
  {
    fclose (fp);
    png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
    if (row_pointers) delete row_pointers;
  }
  /* setup libpng for using standard C fread() function
   *      with our FILE pointer */
  png_init_io (png_ptr, fp);
  /* tell libpng that we have already read the magic number */
  png_set_sig_bytes (png_ptr, sizeof (magic));
  /* read png info */
  png_read_info (png_ptr, info_ptr);
  /* get some usefull information from header */
  bit_depth = png_get_bit_depth (png_ptr, info_ptr);
  color_type = png_get_color_type (png_ptr, info_ptr);
  /* convert index color images to RGB images */
  if (color_type == PNG_COLOR_TYPE_PALETTE)
    png_set_palette_to_rgb (png_ptr);
  /* convert 1-2-4 bits grayscale images to 8 bits
   *      grayscale. */
  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
    png_set_gray_1_2_4_to_8 (png_ptr);
  if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
    png_set_tRNS_to_alpha (png_ptr);
  if (bit_depth == 16)
    png_set_strip_16 (png_ptr);
  else if (bit_depth < 8)
    png_set_packing (png_ptr);
  /* update info structure to apply transformations */
  png_read_update_info (png_ptr, info_ptr);
  /* retrieve updated information */
  png_get_IHDR (png_ptr, info_ptr,(png_uint_32*)(&img->sizeX),(png_uint_32*)(&img->sizeY),&bit_depth, &color_type,NULL, NULL, NULL);
  /* get image format and components per pixel */
  if(color_type != PNG_COLOR_TYPE_RGB_ALPHA)
  {
    cout << "png : espace de couleur different de RGBA" << endl;
    fclose(fp);
    return;
  }
  /* we can now allocate memory for storing pixel data */
  img->data = new unsigned char[img->sizeX * img->sizeY * 4];
  /* setup a pointer array.  Each one points at the begening of a row. */
  row_pointers = new png_bytep[img->sizeY];
  for (i = 0; i < img->sizeY; i++)
  {
    row_pointers[i] = (png_bytep)((img->data) + i*(img->sizeX)*4);
  }
  /* read pixel data using row pointers */
  png_read_image (png_ptr, row_pointers);
  /* finish decompression and release memory */
  png_read_end (png_ptr, NULL);
  png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
  /* we don't need row pointers anymore */
  delete[] (row_pointers);
  fclose (fp);
}






int ImageLoader::loadTGA(const char *name, Image * img)
{
    GLenum texFormat;
    unsigned char type[4];
    unsigned char info[7];
    unsigned char *imageData = NULL;
    int imageWidth, imageHeight;
    int imageBits, size;
    FILE *s;

    if (!(s = fopen (name, "r+bt")))
        return TGA_FILE_NOT_FOUND;

    fread (&type, sizeof (char), 3, s); // read in colormap info and image type, byte 0 ignored
    fseek (s, 12, SEEK_SET);			// seek past the header and useless info
    fread (&info, sizeof (char), 6, s);

    if (type[1] != 0 || (type[2] != 2 && type[2] != 3))
    {
	    fclose(s); return TGA_BAD_IMAGE_TYPE;
    }

    imageWidth = info[0] + info[1] * 256; 
    imageHeight = info[2] + info[3] * 256;
    imageBits =	info[4]; 

    size = imageWidth * imageHeight; 

    /* make sure dimension is a power of 2 */
    if (!(imageWidth == 2 || imageWidth == 4 || imageWidth == 8	 || imageWidth == 16 ||imageWidth == 32  || imageWidth == 64 ||imageWidth == 128 || imageWidth == 256 || imageWidth == 512 || imageWidth ==1024) || !(imageHeight == 2	 || imageHeight == 4 || imageHeight == 8 || imageHeight == 16 || imageHeight == 32  || imageHeight == 64 ||imageHeight == 128 || imageHeight == 256 || imageHeight == 512 || imageHeight==1024))
    {
	fclose(s);return TGA_BAD_DIMENSION;
    }

    /* make sure we are loading a supported type */
    if (imageBits != 32 && imageBits != 24 && imageBits != 8)
    {
    	fclose(s);return TGA_BAD_BITS;
    }


unsigned char temp;
int bread;
int i;


switch (imageBits)
{
	case 32:    imageData = (unsigned char*)malloc (size * 4); 

		    if ( imageData == NULL)
		    {return 0;}

		    bread = fread (imageData, sizeof (unsigned char), size * 4, s); 

		    /* TGA is stored in BGRA, make it RGBA */
		    if (bread != size * 4)
		    {
		        free (imageData);
		        return 0;
		    }

		    for (i = 0; i < size * 4; i += 4 )
		    {
		        temp = imageData[i];
		        imageData[i] = imageData[i + 2];
		        imageData[i + 2] = temp;
		    }
		
		    texFormat = GL_RGBA;
		 break;

	case 24:    imageData = (unsigned char*)malloc (size * 3); 

		    if (imageData == NULL)
		        return 0;

		    bread = fread (imageData, sizeof (unsigned char), size * 3, s);

		    if (bread != size * 3)
		    {
		        free (imageData);
		        return 0;
		    }

		    /* TGA is stored in BGR, make it RGB */
		    for (i = 0; i < size * 3; i += 3)
		    {
        		temp = imageData[i];
		        imageData[i] = imageData[i + 2];
		        imageData[i + 2] = temp;
		     }

		    texFormat = GL_RGB;
		 break;

	case 8:     imageData = (unsigned char*)malloc (size);

		    if (imageData == NULL)
		        return 0;

		    bread = fread (imageData, sizeof (unsigned char), size, s);

		    if (bread != size)
		    {
		        free (imageData);
		        return 0;
		    }

		    texFormat = GL_ALPHA;
		 break;
	default : imageData = (unsigned char*) NULL;break;
}


    /* no image data */
    if (imageData == NULL)
	{
		fclose(s);return TGA_BAD_DATA;
	}


    fclose (s);

    img->sizeX = imageWidth;
    img->sizeY = imageHeight;
    img->data = imageData;
    img->format = texFormat;

    /* 
    // Chargement de l'image en tant que texture
    glBindTexture (GL_TEXTURE_2D, id);
    glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexImage2D (GL_TEXTURE_2D, 0, texFormat, imageWidth, imageHeight, 0, texFormat, GL_UNSIGNED_BYTE, imageData);

    // release data, its been uploaded
    free (imageData);
*/
    return 1;
}







///////////
//Texture//
///////////
unsigned int  Texture::_compteur = 0;
bool Texture::_init=false;
unsigned int* Texture::_textureTabId = new unsigned int[500];
const unsigned int Texture::_nbMaxTexture=500;

Texture::Texture(const char *file)
{
  _textureNumber=_compteur;//indice de la texture dans le tableau des indices de texture
  _compteur++;
  _mode = GL_MODULATE;
  if(!_init)
  { 
    glGenTextures(_nbMaxTexture, _textureTabId);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _mode);
    _init=true;
  }
  
  _image = new Image();
  if(strstr(file, ".jpg"))
  {
    ImageLoader::loadJPEG(file, _image);
  }
  else if(strstr(file,".bmp"))
  {
    ImageLoader::loadBMP(file, _image);
  }
  else if(strstr(file,".png"))
  {
    ImageLoader::loadPNG(file, _image);
  }
  else if (strstr(file,".tga"))
  {
    ImageLoader::loadTGA(file,_image);
  }


  if(_image && _image->data)
  {
    glBindTexture(GL_TEXTURE_2D, _textureTabId[_textureNumber]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, _image->sizeX, _image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, _image->data);
  }
  
}

Texture::Texture(Image *image)
{
  _compteur++;
  _textureNumber=(_compteur-1);//indice de la texture dans le tableau des indices de texture
  if(!_init)
  { 
    glGenTextures(_nbMaxTexture, _textureTabId);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _mode);
    _init=true;
  }
  
 _image=image;
 if(_image && _image->data)
  {
    glBindTexture(GL_TEXTURE_2D, _textureTabId[_textureNumber]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, _image->sizeX, _image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, _image->data);
  } 
}
Texture::~Texture()
{
  if(_image) delete _image;
}

void Texture::loadTexture()
{
  glBindTexture(GL_TEXTURE_2D, _textureTabId[_textureNumber]);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _mode);
  glTexImage2D(GL_TEXTURE_2D, 0, 4, _image->sizeX, _image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, _image->data);
}



Image* Texture::getImage()
{
  return _image;
}

void Texture::setModePlaquage(int mode)
{
  _mode=mode;
}

int Texture::getModePlaquage()
{
  return _mode;
}
